/************************************************************************
Copyright 2003, 2004, 2005, 2006, 2007, 2008 Grid Technology Research Center,
National Institute of Advanced Industrial Science and Technology.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
************************************************************************/

package jp.aist.gtrc.plus.reserve;

import java.io.FileNotFoundException;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;

import org.apache.log4j.Logger;

import condor.classad.*;

import jp.aist.gtrc.plus.scheduler.node.NodeAllocateRequest;
import jp.aist.gtrc.plus.scheduler.node.NodeInfo;
import jp.aist.gtrc.plus.scheduler.node.NodeManager;
import jp.aist.gtrc.plus.scheduler.status.JobStatus;
import jp.aist.gtrc.plus.scheduler.status.NodeStatus;
import jp.aist.gtrc.plus.scheduler.util.Checker;
import jp.aist.gtrc.plus.scheduler.util.ClassAdEvaluator;
import jp.aist.gtrc.plus.scheduler.util.TextUtil;
import jp.aist.gtrc.plus.scheduler.util.TimeUtil;

public class ReserveClassAdChecker extends Checker<NodeInfo> {

	private HashMap<String, Expr> infoMap;
	private ClassAdEvaluator evaluator;
	private String evalAttr;

	private static final Logger logger = Logger.getLogger(ReserveClassAdChecker.class);
	public static final String DEFAULT_CLASSAD_FILE = "reservable.ad";
	public static final String DEFAULT_EVAL_ATTR_WAITING = "PLUS_NODE_CANDIDATE_OK";
	public static final String DEFAULT_EVAL_ATTR_RUNNING = "PLUS_NODE_RESERVABLE";
	public static final String DEFAULT_EVAL_ATTR = DEFAULT_EVAL_ATTR_RUNNING;
	private static final boolean DEFAULT_RESERVABILITY = true;

	protected ReserveClassAdChecker(NodeManager nodeMgr,
			String adFilePath, String evalAttr,
			NodeAllocateRequest allocReq) {

		if (TextUtil.isEmpty(adFilePath)) {
			adFilePath = DEFAULT_CLASSAD_FILE;
		}

		try {
			this.evaluator = new ClassAdEvaluator(adFilePath, allocReq);

			this.infoMap = new LinkedHashMap<String, Expr>();
			for (NodeInfo info : nodeMgr.getNodes()) {
				infoMap.put(info.getName(), makeNodeExpr(info));
			}
			this.evaluator.insertAttribute("PLUS_ALL_NODES",
					new ListExpr(new LinkedList<Expr>(infoMap.values())));
		} catch (FileNotFoundException e) {
			logger.debug(adFilePath + " doesn't exist. Always treat reservability as "
					+ DEFAULT_RESERVABILITY);
		}
		this.evalAttr = TextUtil.isEmpty(evalAttr)
			? DEFAULT_EVAL_ATTR_RUNNING : evalAttr;

	}

	protected void addAbsTime(RecordExpr expr, String name, Calendar c) {

		if (c != null) {
			Date d = new Date(c.getTimeInMillis());
			/*
			 * Use Date because getInstance(Date) makes absolute time,
			 * but getInstance(long) makes *relative* time.
			 * getInstance(long msec, int tz) makes absolute time.
			 */
			expr.insertAttribute(name, Constant.getInstance(d));
		}

	}

	protected void addRelTime(RecordExpr expr, String name, long msec) {

		assert(msec >= 0);
		expr.insertAttribute(name, Constant.getInstance(msec));

	}

	protected RecordExpr makeJobExpr(JobStatus job) {

		RecordExpr expr = new RecordExpr();
		expr.insertAttribute("id",
				Constant.getInstance(job.getJobID().toString()));
		expr.insertAttribute("owner",
				Constant.getInstance(job.getOwner()));
		expr.insertAttribute("state",
				Constant.getInstance(job.getState().name()));
		expr.insertAttribute("priority",
				Constant.getInstance(job.getPriority()));

		addAbsTime(expr, "startTime", job.getExecStartTime());

		int walltime = job.getRequestedTime();
		if (walltime != JobStatus.WALLTIME_NOT_SPECIFIED) {
			addRelTime(expr, "wallTime", TimeUtil.secToMilli(walltime));
		}
		return expr;

	}

	protected ListExpr makeJobListOnNode(NodeStatus node) {

		ListExpr jobList = new ListExpr();
		Collection<JobStatus> jobsOnNode = node.getJobs();
		if (jobsOnNode != null) {
			for (JobStatus job : jobsOnNode) {
				Expr jobExpr = makeJobExpr(job);
				jobList.add(jobExpr);
			}
		}

		return jobList;

	}

	private Expr makeNodeExpr(NodeInfo info) {

		RecordExpr expr = new RecordExpr();
		NodeStatus status = info.getStatus();

		expr.insertAttribute("name",
				Constant.getInstance(status.getName()));
		expr.insertAttribute("isAlive",
				Constant.getInstance(status.isAlive()));
		expr.insertAttribute("loadavg",
				Constant.getInstance(status.getLoadAverage()));
		expr.insertAttribute("nRunJobs",
				Constant.getInstance(status.getRunningJobNum()));

		ListExpr jobList = makeJobListOnNode(status);
		expr.insertAttribute("jobs", jobList);

		return expr;

	}

	private Expr makeNodeExprList(Collection<NodeInfo> okInfos) {

		ListExpr list = new ListExpr();
		for (NodeInfo info : okInfos) {
			Expr e = infoMap.get(info.getName());
			if (e != null) {
				list.add(e);
			} else {
				assert(false);
			}
		}
		return list;

	}

	public boolean isOK(NodeInfo info) {
		assert(false);
		return false;
	}

	public boolean isOK(NodeInfo info, Collection<NodeInfo> okInfos) {

		if (evaluator == null) {
			// treat all nodes are reservable if reservable.ad doesn't exist.
			return DEFAULT_RESERVABILITY;
		}

		evaluator.insertAttribute("PLUS_CANDIDATE_NODE",
				infoMap.get(info.getName()));
		evaluator.insertAttribute("PLUS_ALLOCATED_NODES",
				makeNodeExprList(okInfos));

		if ((evaluator.hasAttr(evalAttr) == false)
				&& evalAttr.equals(DEFAULT_EVAL_ATTR_WAITING)) {
			// use DEFAULT_EVAL_ATTR if ATTR_WAITING is not defined
			evalAttr = DEFAULT_EVAL_ATTR;
		}

		Expr value = evaluator.eval(evalAttr);
		logger.debug(evalAttr + " of " + info.getName()
				+ " is " + value.toString());
		return value.isTrue();

	}

}
